/*
    Copyright 2006 Todi.
    Send feedback and suggestions to todi@peerfeeds.com.  
  
    This file is part of Peerfeeds.

    Peerfeeds is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    Peerfeeds is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Peerfeeds; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
  
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Configuration;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.Windows.Forms;
using System.IO;

namespace Peerfeeds
{
    public class BalloonStatus
    {
        public string title = "empty";
        public string text = "empty";
        public ToolTipIcon icon = ToolTipIcon.Info;
    }   
    
    public class Program
    {
        public string versionString = "Peerfeeds 1.1 [b1]";
        private ArrayList errorLog = new ArrayList();

        string xmlSettings = ConfigurationManager.AppSettings["SavePath"];
        ArrayList statusList = new ArrayList();

        int rssErrors = 0;
        int rssSkipped = 0;

        public BalloonStatus RetrieveRSS(ref DateTime lastFeedsRead, ref ArrayList feedsCache)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();

            BalloonStatus status = new BalloonStatus();

            bool rssGet = false;
            bool rssWrite = false;
            bool rssTextcommands = (xmlSettings.IndexOf("text_commands.xml") > -1);

            StringBuilder str = new StringBuilder();
            ArrayList rss = new ArrayList();
            ArrayList feeds = new ArrayList();

            Xml xml = new Xml();


            // If we're supposed to use a text_commands.xml, send the path through to Xml(), otherwise use text files instead
            try
            {
                xml = (rssTextcommands) ? new Xml(xmlSettings) : xml = new Xml();
                rssGet = true;
            }
            catch (Exception f)
            {
                rssGet = false;
                status.title = "Error when looking for 'text_commands.xml'";
                status.text = "Path: " + xmlSettings + Environment.NewLine +
                    "Message: " + f.Message.ToString() + Environment.NewLine + "(Make sure you've read ReadMe.txt properly)";
                status.icon = ToolTipIcon.Error;

                return status;
            }

            // Try to load our Feeds.xml
            if (rssGet)
            {
                try
                {
                    feeds = xml.ReadFeedsFromXML("Feeds.xml", lastFeedsRead); //xml.LoadFeeds();

                    if (feeds.Count == 0)
                    {
                        feeds.AddRange(feedsCache);
                    }
                    else
                    {
                        lastFeedsRead = DateTime.Now;
                    }
                    rssGet = true;
                    feedsCache.Clear();

                }
                catch (Exception f)
                {
                    rssGet = false;
                    status.title = "Error when reading 'Feeds.xml'";
                    status.text = f.Message.ToString();
                    status.icon = ToolTipIcon.Error;

                    return status;
                }
            }


            // Apparently that went well, so we go on with getting the rss
            if (rssGet)
            {
                if (!File.Exists("templates" + Path.DirectorySeparatorChar + ConfigurationManager.AppSettings["Template"]))
                {
                    GenerateDefaultTemplate();
                }

                // Run through all loaded feeds and retrieve them
                foreach (Xml.Feed feed in feeds)
                {
                    Xml.Feed loc = feed;
                    str = new StringBuilder();

                    // Check which template we should use
                    if (feed.template != "" && File.Exists("templates" + Path.DirectorySeparatorChar + feed.template))
                    {
                        xml.Template("templates" + Path.DirectorySeparatorChar + feed.template);
                    }
                    else
                    {
                        xml.Template("templates" + Path.DirectorySeparatorChar + ConfigurationManager.AppSettings["Template"]);
                    }

                    // Retrieve the feeds
                    try
                    {
                        rss = ReadRss(ref loc, ref rssWrite);
                    }
                    catch
                    {
                        loc.lastModified = new DateTime();
                    }

                    feedsCache.Add(loc);

                    if (rssWrite && rss.Count > 0)
                    {
                        // Add header
                        Hashtable ha = (Hashtable)rss[0];
                        str.Append(Cleaner(HashtableToString(ha, xml.header), feed.stripHTML));

                        statusList.Add(ha);

                        // Add items
                        if (feed.reverse)
                        {
                            for (int i = (rss.Count - 1); i > -1; i--)
                            {
                                ha = (Hashtable)rss[i];
                                str.Append(Cleaner(HashtableToString(ha, xml.item), feed.stripHTML));
                            }
                        }
                        else
                        {
                            for (int i = 1; i < rss.Count; i++)
                            {
                                ha = (Hashtable)rss[i];
                                str.Append(Cleaner(HashtableToString(ha, xml.item), feed.stripHTML));
                            }
                        }

                        xml = WriteRss(rssTextcommands, xml, feed.textCommand, str.ToString());

                    }

                }


                // Write statusmessage
                string statusCmd = ConfigurationManager.AppSettings["StatusName"];

                if (statusCmd != "")
                {
                    xml.Template("templates" + Path.DirectorySeparatorChar + statusCmd + ".template");
                    str = new StringBuilder();

                    // Add header
                    Hashtable ha = new Hashtable();
                    str.Append(HashtableToString(ha, xml.header));

                    // Add items
                    for (int i = 0; i < feedsCache.Count; i++)
                    {
                        Xml.Feed feed = (Xml.Feed)feedsCache[i];
                        ha = feed.header;
                        str.Append(Cleaner(HashtableToString(ha, xml.item), true));
                    }

                    if (statusList.Count == 0)
                    {
                        str.Append("No feeds found.");
                    }

                    statusList.Clear();
                    xml = WriteRss(rssTextcommands, xml, statusCmd, str.ToString());
                }

                // Write our changed xmldoc to the textcommands
                if (rssTextcommands && (feeds.Count > rssErrors || statusCmd != ""))
                {
                    xml.WriteXmlDoc(xmlSettings);
                }

                sw.Stop();
                TimeSpan duration = sw.Elapsed;

                // Set title
                status.title = "RSS feed update completed";           

                // Display status
                status.text = "Retrieved " + (feeds.Count - rssErrors - rssSkipped) + " feed(s) at " + System.DateTime.Now.ToLongTimeString() + ". (" + duration.Seconds.ToString() + " secs)";

                if (rssSkipped > 0)
                {
                    status.text += Environment.NewLine + rssSkipped.ToString() + " feed(s) had not been updated since last check.";
                }

                if (rssErrors > 0)
                {
                    status.text += Environment.NewLine + "There were " + rssErrors.ToString() + " error(s).";
                }

            }
            return status;
        }


        // Generate default template
        private void GenerateDefaultTemplate()
        {

        }


        // Retrieve RSS data
        private ArrayList ReadRss(ref Xml.Feed feed, ref bool rssWrite)
        {
            ArrayList rss = new ArrayList();
            Rss rssRead = new Rss();

            // Retrieve the feed
            //int attempts = 2;

            // Feed can fail sometimes, network congestion and whatnot, so we try twice before giving up
            //while (attempts > 0)
            //{
            try
            {
                rss = rssRead.RssReader(feed.url, feed.items, ref feed.lastModified);
                //attempts = 0;

                rssWrite = true;

                // Add feed-specific variables
                Hashtable header = (Hashtable)rss[0];
                rss.RemoveAt(0);

                header.Add("%Feed[URL]", feed.url);
                header.Add("%Feed[TextCommand]", feed.textCommand);
                header.Add("%Feed[MaxItems]", feed.items);
                header.Add("%Feed[StripHTML]", feed.stripHTML.ToString());
                rss.Insert(0, header);

                feed.header = header;

            }
            catch (System.Xml.XmlException f)
            {
                errorLog.Add("[" + DateTime.Now.ToString("R") + "] " + feed.textCommand + ": " + f.Message);
                rssErrors++;
                throw;
            }
            catch (Exception f)
            {
                if (f.Message == "The remote server returned an error: (304) Not Modified.")
                {
                    //AddError(feed.textCommand + ": " + f.Message);
                    rssSkipped++;
                    return rss;
                }
                else //if (attempts == 1)
                {   //[Fri Nov 24 12:09:49 2006]
                    errorLog.Add("[" + DateTime.Now.ToString("R") + "] " + feed.textCommand + ": " + f.Message);
                    rssErrors++;
                    throw;
                }
                //attempts--;
            }
            //}

            return rss;
        }


        // Write RSS data
        private Xml WriteRss(bool rssTextcommands, Xml xml, string textCommand, string data)
        {
            // Now we need to write the content to text_command.xml/textfiles
            try
            {
                // Check if we're writing to text_commands.xml or to textfiles
                if (rssTextcommands)
                {
                    xml.WriteTextCommand(textCommand, data);
                }
                else
                {
                    // Write to textfiles
                    xml.WriteTextFile(xmlSettings + textCommand, data);
                }
            }
            catch (Exception f)
            {
                errorLog.Add("[" + DateTime.Now.ToString("R") + "] " + textCommand + ": " + f.Message.ToString());
                rssErrors++;
            }

            return xml;
        }


        // Retrieve the header and items from the hashtables
        private string HashtableToString(Hashtable ha, string item)
        {
            if (ha != null && ha.Count > 0)
            {
                IDictionaryEnumerator en = ha.GetEnumerator();

                while (en.MoveNext())
                {
                    item = item.Replace(en.Key.ToString(), en.Value.ToString());
                }

                // Strip away %variables% and %varia[bles]% since we don't have any data for them
                item = Regex.Replace(item, "%\\S+%", "");

                // Check for DateTime strings and replace them with actual datetime
                // See http://www.dotnetspider.com/kb/Article276.aspx for reference
                Match timeFormat = Regex.Match(item, "%DateTime\\[(.+)\\]");
                string insert = DateTime.Now.ToString(timeFormat.Groups[1].Value);
                item = Regex.Replace(item, "%DateTime\\[.+\\]", insert);
            }
                return item;
        }


        // Clean up the rss data
        private string Cleaner(string data, bool stripHTML)
        {
            // Do a regular expressions check to remove HTML (thanks Ghostface)
            if (stripHTML)
            {
                // Change <br>'s into dot space to make html feeds look better
                data = Regex.Replace(data, "(<br[ /]*>)", ". ", RegexOptions.IgnoreCase);

                // Remove &nbsp; It looks ugly and is illegal in xml
                data = data.Replace("&nbsp;", " ");

                // Ditto for &mdash;
                data = data.Replace("&mdash;", "--");

                // Remove all other html we can find
                data = Regex.Replace(data, "<[a-zA-Z\\/\\!][^>]*>", "", RegexOptions.IgnoreCase);
                //s/<[a-zA-Z\/][^>]*>//g

                // Probably need more stuff here. Time will tell
            }
            return data;
        }

        public void WriteErrors(string fileName)
        {
            FileStream fs = new FileStream(@fileName, FileMode.OpenOrCreate, FileAccess.Read);

            StreamReader reader = new StreamReader(fs, Encoding.Default);
            string cont = reader.ReadToEnd();
            reader.Close();
            fs.Close();

            StringBuilder str = new StringBuilder();

            foreach (string s in errorLog)
            {
                str.AppendLine(s);
            }

            cont = str.ToString() + cont;

            fs = new FileStream(@fileName, FileMode.Truncate, FileAccess.Write);
            StreamWriter writer = new StreamWriter(fs, Encoding.Default);
            writer.Write(cont);
            writer.Close();
            fs.Close();
        }

    }
}
